export type RGBA = [r: number, g: number, b: number, a: number];
 
export class MipmapGenerator {
    protected originalWidth: number;
    protected originalHeight: number;
    protected mipLevels: Uint8ClampedArray[] = [];
    protected mipSizes: [number, number][] = [];
 
    constructor(imageData: Uint8ClampedArray, width: number, height: number) {
        this.originalWidth = width;
        this.originalHeight = height;
        this.generateMipmaps(imageData, width, height);
    }
 
    private generateMipmaps(
        imageData: Uint8ClampedArray,
        width: number,
        height: number,
        level = 0
    ): void {
        const levelData = new Uint8ClampedArray(imageData);
        this.mipLevels.push(levelData);
        this.mipSizes.push([width, height]);
 
        if (width === 1 && height === 1) return;
 
        const nextWidth = Math.max(1, Math.floor(width / 2));
        const nextHeight = Math.max(1, Math.floor(height / 2));
        const nextData = new Uint8ClampedArray(nextWidth * nextHeight * 4);
 
        this.bilinearDownscale(
            imageData, width, height,
            nextData, nextWidth, nextHeight
        );
 
        this.generateMipmaps(nextData, nextWidth, nextHeight, level + 1);
    }
 
    protected bilinearDownscale(
        src: Uint8ClampedArray,
        srcWidth: number,
        srcHeight: number,
        dst: Uint8ClampedArray,
        dstWidth: number,
        dstHeight: number
    ): void {
        const ratioX = srcWidth / dstWidth;
        const ratioY = srcHeight / dstHeight;
 
        for (let y = 0; y < dstHeight; y++) {
            for (let x = 0; x < dstWidth; x++) {
                const srcX = (x + 0.5) * ratioX - 0.5;
                const srcY = (y + 0.5) * ratioY - 0.5;
 
                const x0 = Math.floor(srcX);
                const y0 = Math.floor(srcY);
                const x1 = Math.min(x0 + 1, srcWidth - 1);
                const y1 = Math.min(y0 + 1, srcHeight - 1);
 
                const dx = srcX - x0;
                const dy = srcY - y0;
 
                for (let c = 0; c < 4; c++) {
                    const topLeft = src[((y0 * srcWidth) + x0) * 4 + c];
                    const topRight = src[((y0 * srcWidth) + x1) * 4 + c];
                    const bottomLeft = src[((y1 * srcWidth) + x0) * 4 + c];
                    const bottomRight = src[((y1 * srcWidth) + x1) * 4 + c];
 
                    const top = topLeft * (1 - dx) + topRight * dx;
                    const bottom = bottomLeft * (1 - dx) + bottomRight * dx;
                    const interpolated = Math.round(top * (1 - dy) + bottom * dy);
 
                    dst[(y * dstWidth + x) * 4 + c] = interpolated;
                }
            }
        }
    }
 
    public getLevel(level: number): Uint8ClampedArray | null {
        return level >= 0 && level < this.mipLevels.length 
            ? this.mipLevels[level] 
            : null;
    }
 
    public findBestLevel(targetWidth: number, targetHeight: number): number {
        let bestLevel = 0;
        let minDiff = Infinity;
 
        for (let i = 0; i < this.mipSizes.length; i++) {
            const [w, h] = this.mipSizes[i];
            const diff = Math.abs(w - targetWidth) + Math.abs(h - targetHeight);
            if (diff < minDiff) {
                minDiff = diff;
                bestLevel = i;
            }
        }
 
        return bestLevel;
    }
 
    public getLevelCount(): number {
        return this.mipLevels.length;
    }
 
    public getLevelSize(level: number): [number, number] | null {
        return level >= 0 && level < this.mipSizes.length 
            ? this.mipSizes[level] 
            : null;
    }
}